package top.codedance.iotp.client.manage.net.com232;

import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.client.manage.ManageApplication;
import top.codedance.iotp.common.util.SystemUtil;
import com.fazecast.jSerialComm.SerialPort;
import org.apache.log4j.Logger;

import javax.xml.bind.DatatypeConverter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class UploadRunner implements Runner {
    private Logger logger = ManageApplication.getLogger(UploadRunner.class);
    private static volatile Map<String, Long> tasksOverLength = new HashMap<>();
    private static volatile Map<String, Map<Integer, File>> tasksTempFile = new HashMap<>();
    private static volatile Map<String, Thread> tasksJoiner = new HashMap<>();

    private synchronized void initData(String taskId) {
        if (!tasksOverLength.containsKey(taskId)) {
            tasksOverLength.put(taskId, 0l);
            tasksTempFile.put(taskId, new HashMap<>());
        }
    }

    private synchronized Long getTasksOverLength(String taskId) {
        return tasksOverLength.get(taskId);
    }

    private synchronized void accumulation(String taskId, int length) {
        tasksOverLength.put(taskId, tasksOverLength.get(taskId) + length);
    }

    private synchronized void addTempFile(String taskId, Integer dataIndex, File file) {
        Map<Integer, File> integerFileMap = tasksTempFile.get(taskId);
        integerFileMap.put(dataIndex, file);
        tasksTempFile.put(taskId, integerFileMap);
    }

    private synchronized boolean existsTempFile(String taskId, Integer dataIndex) {
        if (tasksTempFile.containsKey(taskId)) {
            return tasksTempFile.get(taskId).containsKey(dataIndex);
        } else {
            return false;
        }
    }

    private synchronized void addJoinner(String cTaskId, Thread t) {
        tasksJoiner.put(cTaskId, t);
    }

    private synchronized void removeJoinner(String cTaskId) {
        tasksJoiner.remove(cTaskId);
    }

    private synchronized boolean existsJoinner(String cTaskId) {
        return tasksJoiner.containsKey(cTaskId);
    }

    @Override
    public byte[] exec(byte[] data) {
        JSONObject result = new JSONObject();
        try {
            String jsonStr = new String(data, StandardCharsets.UTF_8);
            JSONObject jsonObject = JSONObject.parseObject(jsonStr);
            final int opration = jsonObject.getIntValue("opration");
            final String cTaskId = jsonObject.getString("taskId");
            final String runtimeDirPath = SystemUtil.getRuntimeDirPath(ManageApplication.class);
            String tempDirPath = runtimeDirPath + "upload/" + cTaskId + "/";
            final File tempDir = new File(tempDirPath);
            if (opration == 1) {
                Map<Integer, File> taskTempMap = tasksTempFile.get(cTaskId);
                for (int i = 0; i < taskTempMap.size(); i++) {
                    File file2 = taskTempMap.get(i);
                    if (file2.exists()) {
                        file2.delete();
                    }
                }
                tempDir.delete();
                tasksTempFile.remove(cTaskId);
                result.put("taskId", cTaskId);
                result.put("msg", "cancel success.");
            }
            if (opration == 0) {
                final Integer dataIndex = jsonObject.getInteger("dataIndex");
                final Long contentLength = jsonObject.getLong("contentLength");
                int readLength = jsonObject.getIntValue("bodyLength");
                byte[] body = DatatypeConverter.parseHexBinary(jsonObject.getString("body"));
                boolean cover = jsonObject.getBoolean("cover");
                String target = jsonObject.getString("target");
                String filename = jsonObject.getString("filename");
                final String endClientFilePath = target + (target.endsWith(String.valueOf(File.separatorChar)) ? "" : File.separatorChar) + filename;
                if (!tempDir.exists()) {
                    tempDir.mkdirs();
                }
                synchronized (this.getClass()) {
                    if (!tasksOverLength.containsKey(cTaskId)) {
                        initData(cTaskId);
                    }
                }

                if (!existsTempFile(cTaskId, dataIndex)) {
                    String downloadTempFragmentPath = tempDirPath + dataIndex;
                    File tmpWriteFile = new File(downloadTempFragmentPath);
                    tmpWriteFile.createNewFile();
                    FileOutputStream fos = new FileOutputStream(tmpWriteFile);
                    fos.write(body);
                    fos.flush();
                    fos.close();
                    accumulation(cTaskId, readLength);
                    addTempFile(cTaskId, dataIndex, tmpWriteFile);
                    double percentage = Math.floor(getTasksOverLength(cTaskId) * 1.0 / contentLength * 100);
                    result.put("taskId", cTaskId);
                    result.put("percentage", percentage);
                    logger.debug("Download complete : " + percentage + "%");
                }

                synchronized (this.getClass()) {
                    if (!existsJoinner(cTaskId)) {
                        Thread t = new Thread(() -> {
                            while (true) {
                                if (getTasksOverLength(cTaskId) >= contentLength) {
                                    try {
                                        logger.debug("Start join download file to: " + endClientFilePath);
                                        Map<Integer, File> taskTempMap = tasksTempFile.get(cTaskId);
                                        File file = new File(endClientFilePath);
                                        if (file.exists()) {
                                            if(cover) {
                                                file.delete();
                                            }else{
                                                file.renameTo(new File(file.getAbsolutePath() + ".bak"));
                                            }
                                        }
                                        FileChannel resultFileChannel = new FileOutputStream(file, true).getChannel();
                                        for (int i = 0; i < taskTempMap.size(); i++) {
                                            File file2 = taskTempMap.get(i);
                                            FileChannel blk = new FileInputStream(file2).getChannel();
                                            resultFileChannel.transferFrom(blk, resultFileChannel.size(), blk.size());
                                            blk.close();
                                            logger.debug("Join complete : " + Math.round(i * 1.0 / taskTempMap.size() * 100) + "%");
                                        }
                                        resultFileChannel.close();
                                        tasksOverLength.remove(cTaskId);
                                        tasksTempFile.remove(cTaskId);
                                        for (int i = 0; i < taskTempMap.size(); i++) {
                                            File file2 = taskTempMap.get(i);
                                            if (file2.exists()) {
                                                file2.delete();
                                            }
                                        }
                                        File parentFile = tempDir.getParentFile();
                                        tempDir.delete();
                                        parentFile.delete();
                                        removeJoinner(cTaskId);
                                        logger.debug("Upload complete.");
                                        break;
                                    } catch (Exception e) {
                                        logger.debug("Upload fail.");
                                    }
                                } else {
                                    try {
                                        Thread.sleep(1000);
                                    } catch (Exception e) {
                                    }
                                }
                            }
                        });
                        synchronized (this.getClass()) {
                            if (!existsJoinner(cTaskId)) {
                                addJoinner(cTaskId, t);
                                t.start();
                            }
                        }
                    }
                }
            }
            JSONObject jo = new JSONObject();
            jo.put("ok", true);
            jo.put("context", result);
            return jo.toJSONString().getBytes(StandardCharsets.UTF_8);
        } catch (Exception e) {
            JSONObject jo = new JSONObject();
            jo.put("ok", false);
            jo.put("msg", e.getMessage());
            logger.debug("Download client part fail.");
            return jo.toJSONString().getBytes(StandardCharsets.UTF_8);
        }
    }

    @Override
    public byte[] exec(byte[] data, SerialPort serialPort) {
        return new byte[0];
    }
}
